#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>  // alloc_chrdev_region
#include <linux/cdev.h>
#include <linux/slab.h>  // kmalloc/kfree
#include <linux/seq_file.h>   // /proc entries using seq infra
#include <linux/proc_fs.h>  // create_proc_entry
#include <linux/sched.h>
#include <linux/wait.h>   // wait queues
#include <linux/semaphore.h>
#include <asm/uaccess.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Manik Singhal");
MODULE_DESCRIPTION("Sample character device driver");


/*
* Parameters accepted by module.
* Initialize with default values
*
* module_param(name, type, permission):
* Default types acceptable by module
* bool, invbool, charp, int, long, short,
* uint, ulong, ushort
*
* permission: S_IRUGO: cannot be changed by world
* S_IRUGO|S_IWUSR: root can change the parameter
* (if permission != 0, parameter appears in /sys/module*,
*  can be changed there)
*/
#define DEV_COUNT    2  // num char-devices
static char *driver_name = "my_char";
static int num_devices = DEV_COUNT;
module_param(driver_name, charp, S_IRUGO);
module_param(num_devices, int, S_IRUGO);
// google module_param_array for accepting array parameters


/*
* My dummy device information
*/
#define MAX_DATA   2048
typedef struct
{
    /* ....
     * ....
     * struct cdev cdev
    */

    struct semaphore sem;  // exclusion b/w multiple readers-writers
    wait_queue_head_t wait_q;  // q to block reader/writer

    char data_area[MAX_DATA];
    int reader_index;    // Where to read from
    int writer_index;    // Where to write to next
    
    int device_no;  // say, device number
    bool inited;
    
    struct cdev cdev;
}dev_info_t;

/*
* General global variables
*/
bool successful_init = false;
dev_t my_dev;
dev_info_t *dev_info;
struct file_operations g_fops;


//###################### MODULE EXPORTS #################################3
loff_t llseek(struct file *fp, loff_t offset, int whence)
{
    return 0;
}

ssize_t read(struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
    dev_info_t *dev = (dev_info_t *)fp->private_data;
    ssize_t size_to_copy = 0;
    int rd, wr;

    if (!dev) {
        return EINVAL;
    }

    if (!size) {
       return 0;
    }

    rd = 0;
    wr = 1;

    do {

        if (rd == wr) {
	    // re-entered loop. Release lock
	    up(&dev->sem);
	}

        // Wait for data in queue
        if (wait_event_interruptible(dev->wait_q, 
	       (dev->reader_index != dev->writer_index))) {
	
	    return -ERESTARTSYS;
	}

        // take the device lock to access data-structures
        if (down_interruptible(&dev->sem)) {
            return -ERESTARTSYS;
        }

        rd = dev->reader_index;
        wr = dev->writer_index;

    }while (rd == wr);


    // We have some data to provide to user
    size_to_copy = (wr - rd);
    if (size_to_copy < 0) {
        // ring wrap
        size_to_copy += MAX_DATA;
    }
    
    if (size_to_copy > size) {
        size_to_copy = size;
    }
    
    // Copy now
    if (size_to_copy + rd > MAX_DATA) {
        // Copy in 2 steps
        copy_to_user(buf, &dev->data_area[rd], (MAX_DATA - rd));
        copy_to_user(&buf[MAX_DATA -rd], dev->data_area, (size_to_copy + rd - MAX_DATA));
    } else {
        // single shot copy
        copy_to_user(buf, &dev->data_area[rd], size_to_copy);
    }
    
    dev->reader_index = (dev->reader_index + size_to_copy) & (MAX_DATA - 1);

    if (((wr + 1) & (MAX_DATA - 1)) == rd) {
       // queue was full!! writer may be sleeping => wake up
       wake_up_interruptible(&dev->wait_q);
    }

    up(&dev->sem);

    return size_to_copy;
}

#define Q_FULL(rd, wr)  (((wr + 1) & (MAX_DATA - 1)) == rd)
ssize_t write(struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{
    dev_info_t *dev = (dev_info_t *)fp->private_data;
    ssize_t size_to_copy = 0;
    int rd, wr;

    if (!dev) {
        return EINVAL;
    }

    if (!size) {
       return 0;
    }

    rd = wr = 0;

    do {

        if (Q_FULL(rd, wr)) {
	    // re-entered loop. Release lock
	    up(&dev->sem);
	}

        // Wait for data in queue
        if (wait_event_interruptible(dev->wait_q, 
	       !Q_FULL(dev->reader_index, dev->writer_index))) {
	
	    return -ERESTARTSYS;
	}

        // take the device lock to access data-structures
        if (down_interruptible(&dev->sem)) {
            return -ERESTARTSYS;
        }

        rd = dev->reader_index;
        wr = dev->writer_index;

    }while (Q_FULL(rd, wr));


    // We have some data to provide to user
    size_to_copy = (rd - wr - 1);
    if (size_to_copy < 0) {
        // ring wrap
        size_to_copy += MAX_DATA;
    }
    
    if (size_to_copy > size) {
        size_to_copy = size;
    }
    
    // Copy now
    if (size_to_copy + wr > MAX_DATA) {
        // Copy in 2 steps
        copy_from_user(&dev->data_area[wr], buf, (MAX_DATA - wr));
        copy_from_user(&dev->data_area, &buf[MAX_DATA -wr], (size_to_copy + wr - MAX_DATA));
    } else {
        // single shot copy
        copy_from_user(&dev->data_area[wr], buf, size_to_copy);
    }
    
    dev->writer_index = (dev->writer_index + size_to_copy) & (MAX_DATA - 1);

    if (rd == wr) {
       // queue was empty!! Reader might be blocked. => unblock
       wake_up_interruptible(&dev->wait_q);
    }

    up(&dev->sem);

    return size_to_copy;
}

int open (struct inode *inode, struct file *filep)
{
    dev_info_t *dev = NULL;
    
    // We don't do much here, except
    // Retrieve dev_info structure for the device
    // and put it in a convinient location
    
    printk("<1> Open called\n");

    dev = container_of(inode->i_cdev, dev_info_t, cdev);
    filep->private_data = (void *)dev;
    
    printk("<1> Open called for device %d (%d)\n", dev->device_no, (int)dev);
    return 0;
}

int release(struct inode *inode, struct file *fp)
{
    // We didn't init anything in open, so nothing to cleanup
    printk("<1> Closing device\n");
    return 0;
}
// ###################### MODULE EXPORTS END ##################################





// ##################### PROC ENTRY ##########################################
/*
* Purpose of this section is to demonstrate outputting live data from the driver
* by reading /proc/$driver_name virtual file
*/
static struct seq_operations seq_ops;
static struct file_operations seq_fops;

/*
* seq_start gets called for initialization when read of /proc/$driver_name
* is attempted
* Return data to be printed corresponding to pos (dev_info structure of the device
*   in our case)
*/
static void *my_seq_start(struct seq_file *s, loff_t *pos)
{
   printk("<1> %s: called with pos: %d\n", __FUNCTION__, (int)*pos);
  if (*pos >= num_devices) {
      return NULL;
  } else {
      return &dev_info[*pos];
  }

}

/*
* seq_next: Called by kernel to get next data to be printed
*   v: Data returned by previous call to start/next
*/
static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
     printk("<1> %s: called\n", __FUNCTION__);
    // Update position to next
    *pos = *pos + 1;

    if (*pos >= num_devices) {
        // Reached end of data list to be printed
	return NULL;
    } else {
        return &dev_info[*pos];
    }

}

/*
* Kernel done printing of data.
* Cleanup if any initialization done
*/
static void my_seq_stop(struct seq_file *s, void *v)
{
   printk("<1> %s: called\n", __FUNCTION__);
    // NOthing to cleanup
}

/*
* Actual printing of data item
*/
static int my_seq_show(struct seq_file *s, void *v)
{
    dev_info_t *dev = (dev_info_t *)v;
   printk("<1> %s: called\n", __FUNCTION__);
    
    // Don't use printk,
    // Can use seq_printf, seq_putc, seq_puts etc.
    seq_printf(s, "Device number: %d\n", dev->device_no);
    seq_printf(s, "    Reader index: %d\n", dev->reader_index);
    seq_printf(s, "    Writer index: %d\n", dev->writer_index);

    return 0;

}

static int seq_local_open(struct inode *inode, struct file *file)
{
   printk("<1> %s: called\n", __FUNCTION__);
   return seq_open(file, &seq_ops);
}

static void seq_init(void)
{
    struct proc_dir_entry *proc_entry = NULL;

    seq_ops.start = my_seq_start;
    seq_ops.next =  my_seq_next;
    seq_ops.stop = my_seq_stop;
    seq_ops.show = my_seq_show;

    seq_fops.owner = THIS_MODULE;
    seq_fops.open = seq_local_open;
    seq_fops.read = seq_read;   // kernel supplied functions
    seq_fops.llseek = seq_lseek;  // kernel supplied functions;
    seq_fops.release = seq_release; // kernel supplied functions;

    proc_entry = create_proc_entry(driver_name, 0, NULL);
    if (proc_entry) {
        proc_entry->proc_fops = (const struct file_operations *)&seq_fops;
    }

    return;
}
// #####################PROC ENTRY END ##########################################










/*
* MODULE initialization function
*
* __init prefix: This function is required only
*      at load time, so can be dropped by loader
*      when loading the driver in memory
*/
static int __init hello_init(void)
{
    
    int ret_val = 0;
    int i = 0;
    dev_info_t *dev = NULL;

    // First we see whether we can accumulate enough memory
    // required for our device operations
    dev_info = (dev_info_t *)kmalloc((sizeof(dev_info_t) * num_devices),
        GFP_KERNEL);
    if (!dev_info) {
        // No use continuing
	goto hello_init_done;
    }

    // Initialize g_fops
    for (i = 0; i < sizeof(g_fops); i++) {
        ((char *)&g_fops)[i] = 0;   // memset to 0 :(
    }
    g_fops.owner = THIS_MODULE;
    g_fops.llseek = llseek;
    g_fops.read = read;
    g_fops.write = write;
    g_fops.open = open;
    g_fops.release = release;

    dev = dev_info;
    for (i = 0; i < num_devices; i++) {
       dev->device_no = i;
       dev->inited = false;
       dev++;
    }


    // register for a major device number
    // and check-out in /proc/devices, once loaded
    ret_val = alloc_chrdev_region(&my_dev, 0, num_devices, driver_name);
    if (!ret_val) {
        printk("<1> Allocated region %s with %d devices, major:%d\n", 
	    driver_name, num_devices, MAJOR(my_dev));
    } else {
       // Cleanup initializations till now
       kfree((void *)dev_info);
       goto hello_init_done;
    }

    /*
    * Init stuff done. Now we register our devices with kernel.
    */
    for (i = 0; i < num_devices; i++) {

        dev = &dev_info[i];
        cdev_init(&dev->cdev, &g_fops);
	dev->cdev.owner = THIS_MODULE;
        
	// Kernel shall call these ops for device at runtime
	// We can change this at runtime if required.
	dev->cdev.ops = &g_fops;  

	dev->reader_index = 0;
	dev->writer_index = 0;
	sema_init(&dev->sem, 1);
	init_waitqueue_head(&dev->wait_q);

        ret_val = cdev_add(&dev->cdev, MKDEV(MAJOR(my_dev), i), 1);
	if (!ret_val) {
	    // Successfully registered device
	    // We are already live with the device
	    // and should be ready to accept system calls on it
	    dev->inited = true;
	}

	printk("<1> Device add status for %d is %d\n", i, ret_val);
    }

    // Create a /proc/$driver_name entry for my device
    seq_init();
        
    successful_init = true;

hello_init_done:
    return !successful_init;
}


/*
* Module cleanup function
*
* __exit prefix: Function required only while
*      unloading the module. So if the module is
*      built within the kernel, or the kernel does
*      not allow unloading modules, then this function
*      is discarded
*/
static void __exit hello_exit(void) {
    
    int i = 0;

    // Cleanup in reverse order

    if (successful_init) {

        remove_proc_entry(driver_name, NULL);

        // De-register devices
        for (i = 0; i < num_devices; i++) {

            if (dev_info[i].inited) {
	        cdev_del(&dev_info[i].cdev);
	    }

	}

	kfree((void *)dev_info);

        
        unregister_chrdev_region(my_dev, DEV_COUNT);
	printk("<1> Released region %s\n", driver_name);
    }
}


/*
* Specify Module init and exit functions
*/
module_init(hello_init);
module_exit(hello_exit);
